library(dplyr)
library(ggplot2)
library(ggpubr)
library(emmeans)
library(ggsignif)
library(here)
library(tidyverse)
library(grid)
here::here()
### note the quartz script needs to be fixed, the headers are janky and wrong (had to be manually fixed)
ParS <- read.delim2(here("02_motifs/ParS/data/01_out_01_indiv_hmm1_ParS.tsv"))
## remove text headers
#ParS <- subset(ParS, ParS$motif_id=="ParS_BSub")
ParS$q.value<- as.numeric(ParS$q.value)
ParS$p.value<- as.numeric(ParS$p.value)
ParS$score<- as.numeric(ParS$score)
### remove any duplicate motifs (occasionally a motif will be a palindromic and hit both + and - strands)
ParS <- ParS %>%
group_by(sequence_name, start, stop) %>% # group by coordinates
slice_max(order_by = score, n = 1) %>% # keep only the row with highest score
ungroup()
### phi3T KY030782, spbeta AF020713
## redrock (actino phage with ParABS) GU339467, uses parS sites but of a different type than sporulation ParS
## combine phage metadata with ParS hits. Label any phage that had no ParS hits with "no_hit" in metadata
inph <- read.csv(here("00_data", "inphared_db", "14Apr2025_knownsporestatus.csv"), row.names = 1)
inph$lifestyle <- ifelse(inph$lifestyle=="Temp", "Temperate", inph$lifestyle)
inph$bac.host <- ifelse(inph$sporulation=="Spor", "Sporulating Bacillota", "Nonsporulating Bacillota") #"Asporogenous Bacillota")
inph$bac.host <- ifelse(inph$newgtdb_Phylum=="Bacillota", inph$bac.host, "Non-Bacillota")
inph <- unite(inph, nice, c("lifestyle", "bac.host"), sep=" Phages of ", remove = FALSE )
meta.cats <- unique(select(inph, host_phage_spor, bac.host, lifestyle, nice))
#inph <- select(inph, Accession, Host, Genome.Length..bp., gtdb_f, f_spor, newgtdb_Phylum, host_phage_spor, phage_type, sporulation, lifestyle, nice)
all <- merge(inph, ParS, by.x="Accession", by.y="sequence_name", all.x=TRUE, all.y=FALSE)
all$motif_id[is.na(all$motif_id)] <- "no_hit"
### create binary hit column of 1 for ParS hit, 0 if no hit
all$hit <- ifelse(all$motif_id=="ParS_BSub", 1, 0)
all$q.value[is.na(all$q.value)] <- 1
## subset for JUST BACILLUS since i used just a bacillus parS gene
#all <- subset(all, all$Host=="Bacillus")
#all <- subset(all, all$newgtdb_Phylum =="Bacillota")
library(dplyr)
library(ggplot2)
library(ggpubr)
library(emmeans)
library(ggsignif)
library(here)
library(tidyverse)
library(grid)
library(viridis)
library(PNWColors)
pal7 <- pnw_palette(name="Starfish",n=7,type="discrete")
pal6 <- pnw_palette(name="Starfish",n=6,type="discrete")
pal4 <- pnw_palette(name="Starfish",n=4,type="discrete")
pal3 <- pnw_palette(name="Starfish",n=3,type="discrete")
pal3.moth <- pnw_palette(name="Moth",n=4,type="discrete")
pal4.sunset <- pnw_palette(name="Sunset",n=4,type="discrete")
pal3.sunset <- pnw_palette(name="Sunset",n=3,type="discrete")
pal7.sunset <- pnw_palette(name="Sunset",n=7,type="discrete")
pal6.sunset <- pnw_palette(name="Sunset",n=6,type="discrete")
font_sizes <- # base font size
theme(
plot.title = element_text(size = 18, face = "bold", hjust = 0.5), # main title
axis.title.x = element_text(size = 14, face = "bold"), # x-axis label
axis.title.y = element_text(size = 14, face = "bold"), # y-axis label
axis.text.x = element_text(size = 12), # x-axis tick labels
axis.text.y = element_text(size = 12),
legend.text = element_text(size = 12),
legend.title = element_text(size = 12) # y-axis tick labels
)
here::here()
[1] "/Users/emma/Library/CloudStorage/OneDrive-SharedLibraries-IndianaUniversity/Lennon, Jay - 0000_Bueren/Projects/LifeStyle/PhageLifestyleSporulation"
### note the quartz script needs to be fixed, the headers are janky and wrong (had to be manually fixed)
ParS <- read.delim2(here("02_motifs/ParS/data/01_out_01_indiv_hmm1_ParS.tsv"))
drep <- read.csv(here("02_motifs/ParS/data/Wdb.csv"))
drep$Accession<- gsub('.fna[^_]*$', '', drep$genome)
drep$ref <- "dRep_ref"
drep <- drep[,c(2,4,5)]
## remove text headers
#ParS <- subset(ParS, ParS$motif_id=="ParS_BSub")
ParS$q.value<- as.numeric(ParS$q.value)
ParS$p.value<- as.numeric(ParS$p.value)
ParS$score<- as.numeric(ParS$score)
### remove any duplicate motifs (occasionally a motif will be a palindromic and hit both + and - strands)
ParS <- ParS %>%
group_by(sequence_name, start, stop) %>% # group by coordinates
slice_max(order_by = score, n = 1) %>% # keep only the row with highest score
ungroup()
### phi3T KY030782, spbeta AF020713
## redrock (actino phage with ParABS) GU339467, uses parS sites but of a different type than sporulation ParS
## combine phage metadata with ParS hits. Label any phage that had no ParS hits with "no_hit" in metadata
inph <- read.csv(here("00_data", "inphared_db", "14Apr2025_knownsporestatus.csv"), row.names = 1)
inph$lifestyle <- ifelse(inph$lifestyle=="Temp", "Temperate", inph$lifestyle)
inph$bac.host <- ifelse(inph$sporulation=="Spor", "Sporulating Bacillota", "Nonsporulating Bacillota") #"Asporogenous Bacillota")
inph$bac.host <- ifelse(inph$newgtdb_Phylum=="Bacillota", inph$bac.host, "Non-Bacillota")
inph <- unite(inph, nice, c("lifestyle", "bac.host"), sep=" Phages of ", remove = FALSE )
inph <- merge(inph, drep, by="Accession", all=TRUE)
inph$ref[is.na(inph$ref)] <- "no_hit"
inph$ref <- ifelse(inph$newgtdb_Phylum=="Bacillota" & inph$ref=="no_hit", "dup", inph$ref)
meta.cats <- unique(select(inph, host_phage_spor, bac.host, lifestyle, nice))
#inph <- select(inph, Accession, Host, Genome.Length..bp., gtdb_f, f_spor, newgtdb_Phylum, host_phage_spor, phage_type, sporulation, lifestyle, nice)
all <- merge(inph, ParS, by.x="Accession", by.y="sequence_name", all.x=TRUE, all.y=FALSE)
all$motif_id[is.na(all$motif_id)] <- "no_hit"
### create binary hit column of 1 for ParS hit, 0 if no hit
all$hit <- ifelse(all$motif_id=="ParS_BSub", 1, 0)
all$q.value[is.na(all$q.value)] <- 1
## subset for JUST BACILLUS since i used just a bacillus parS gene
#all <- subset(all, all$Host=="Bacillus")
#all <- subset(all, all$newgtdb_Phylum =="Bacillota")
### remove bacillus duplicates
all <- subset(all, all$ref!="dup")
pal.cust <- c("#24492e", "#59629b", "#ba7999")
pal.cust2 <- c("#24492e", "#2c6184", "#89689d")
as.data.frame(pal7)
#24492e
#### Q FILTERING
ParS.qual <- all
#ParS.qual$hit <- ifelse(ParS.qual$q.value<1e-4, 1, 0)
ParS.qual$hit <- ifelse(ParS.qual$q.value<1e-4, 1, 0)
## create binary list of phages w/ and w/out ParS hits
ParS.pos <- ParS.qual %>% group_by(Accession, host_phage_spor) %>%
summarise(total.ParS.hits = sum(hit), pos.ParS.hit = max(hit), .groups = "drop") #%>%
ParS.pos %>% count(host_phage_spor)
## create binary list of phages w/ and w/out ParS hits
ParS.sanity <- ParS.qual %>% group_by(Accession, host_phage_spor, Host, Description, Lowest.Taxa, Genus, Family, Genome.Length..bp., molGC....) %>%
summarise(total.ParS.hits = sum(hit), pos.ParS.hit = max(hit), .groups = "drop") #%>%
only.pos <- subset(ParS.sanity, ParS.sanity$total.ParS.hits>0)
ParS.pos %>% count(host_phage_spor)
ParS.summary <- ParS.pos %>%
group_by(host_phage_spor) %>%
summarise(
total_ParS_hits = sum(total.ParS.hits, na.rm = TRUE),
Phage_has_ParS = sum(pos.ParS.hit, na.rm = TRUE),
total.phage = n(),
.groups = "drop"
) %>%
mutate(ParS.pos.perc = Phage_has_ParS / total.phage * 100)
#### Q FILTERING
ParS.qual <- all
ParS.qual$hit <- ifelse(ParS.qual$q.value<1e-4, 1, 0)
#ParS.qual$hit <- ifelse(ParS.qual$q.value<1e-5, 1, 0)
## create binary list of phages w/ and w/out ParS hits
ParS.pos <- ParS.qual %>% group_by(Accession, host_phage_spor) %>%
summarise(total.ParS.hits = sum(hit), pos.ParS.hit = max(hit), .groups = "drop") #%>%
ParS.pos %>% count(host_phage_spor)
## create binary list of phages w/ and w/out ParS hits
ParS.sanity <- ParS.qual %>% group_by(Accession, host_phage_spor, Host, Description, Lowest.Taxa, Genus, Family, Genome.Length..bp., molGC....) %>%
summarise(total.ParS.hits = sum(hit), pos.ParS.hit = max(hit), .groups = "drop") #%>%
only.pos <- subset(ParS.sanity, ParS.sanity$total.ParS.hits>0)
ParS.pos %>% count(host_phage_spor)
ParS.summary <- ParS.pos %>%
group_by(host_phage_spor) %>%
summarise(
total_ParS_hits = sum(total.ParS.hits, na.rm = TRUE),
Phage_has_ParS = sum(pos.ParS.hit, na.rm = TRUE),
total.phage = n(),
.groups = "drop"
) %>%
mutate(ParS.pos.perc = Phage_has_ParS / total.phage * 100)
statistics
ParS.bin <- ParS.pos[,c(1,2,4)]
#ParS.hits <- ParS.hits.all
ParS.hits <- subset(ParS.qual, ParS.qual$hit==1)
#ParS.hits <- subset(ParS.hits.all, ParS.hits.all$phage_type=="Lytic_Spor")
## find center phage genome (whole genome /2)
ParS.hits$seq.mdpt <- as.numeric(ParS.hits$Genome.Length..bp.)/2
## find center of motif
ParS.hits$motif.mdpt <- (ParS.hits$stop + ParS.hits$start )/ 2
### subtract midpoint motif from sequence midpoint to see how far away they are
ParS.hits <- ParS.hits %>%
mutate(mdpt.align = (motif.mdpt - seq.mdpt))
ParS.hits$mdpt.align.kbp <- ParS.hits$mdpt.align/1000
#### TO GET RELATIVE motif alignmen
# Relative position as fraction of genome length
# (-0.5 = start, 0 = center, +0.5 = end)
ParS.hits$rel.mdpt <- (ParS.hits$motif.mdpt - ParS.hits$seq.mdpt) / ParS.hits$Genome.Length..bp.
# Relative position from genome start (0 to 1)
ParS.hits$rel.frac <- ParS.hits$motif.mdpt / ParS.hits$Genome.Length..bp.
# Optionally convert to percentage
ParS.hits$rel.percent <- ParS.hits$rel.frac * 100
##Set specific order for bacterial hosts to appear on graphs
ParS.hits$nice <- factor(ParS.hits$nice, levels = c('Lytic Phages of Sporulating Bacillota', 'Temperate Phages of Sporulating Bacillota', 'Lytic Phages of Nonsporulating Bacillota', 'Temperate Phages of Nonsporulating Bacillota', 'Lytic Phages of Non-Bacillota', 'Temperate Phages of Non-Bacillota' ),ordered = TRUE)
ggplot(ParS.hits, aes(x = mdpt.align.kbp, fill = nice)) +
geom_histogram(binwidth = 5, position = "dodge") +
geom_vline(xintercept = 0, linetype = "dotted") +
scale_x_continuous(breaks = seq(-90, 90, by = 30)) +
labs(x = "ParS distance (kb) from center of phage genome", y = "# of ParS hits") +
facet_wrap(~ nice, ncol = 2)+ ggtitle("Absolute Position of ParS Motif on Phage Genomes") + theme(legend.position="none")

#ggsave(here("02_motifs/ParS/figs/AbsoluteParSposition_1e5.png"))
ggplot(ParS.hits, aes(x = rel.mdpt, fill = nice)) +
geom_histogram(binwidth = 0.05, position = "dodge") +theme_classic() +
geom_vline(xintercept = 0, linetype = "dotted") +
scale_x_continuous(breaks = seq(-0.5, 0.5, by = 0.25)) +
labs(x = "ParS position relative to center of phage genome", y = "# of ParS hits") +
facet_wrap(~ nice, ncol = 2) + theme(legend.position="none") + scale_fill_manual(values=(pal7)) + font_sizes #+ ggtitle("Relative Position of ParS Motif on Phage Genomes")
ggsave(here("02_motifs/ParS/figs/RelativeParSposition_1e4.png"), width=10, height=6)

#ggsave(here("02_motifs/ParS/figs/RelativePatheme_classic()rSposition_1e4.png"))
pd <- position_dodge(width = 0.6) # adjust width as needed
ggplot(fig.meta, aes(x = lifestyle, y = prob, color = bac.host)) +
geom_point(size = 3, position = pd) + ylim(0,0.20)+
geom_errorbar(aes(ymin = asymp.LCL, ymax = asymp.UCL),
width = 0.2, position = pd) +
ylab("Probability of 1 or more ParS \n binding sites in phage genome") +
xlab("Bacterial Host") +
labs(color = "Phage Lifestyle") +
theme_classic(base_size = 14) +
guides(colour = guide_legend(reverse = FALSE))
ggplot(fig.meta, aes(x = bac.host, y = prob, color=lifestyle)) +
geom_point(size = 3) + ylim(0,0.15)+
geom_errorbar(aes(ymin = asymp.LCL, ymax = asymp.UCL), width = 0.2) +
ylab("Probabilty of 1 or more ParS \n binding sites in phage genome") +
xlab("Bacterial Host") + labs(color = "Phage Lifestyle") + theme_classic(base_size = 14) + guides(colour = guide_legend(reverse=F))
#ggtitle("Motif enrichment across treatments")+ guide = guide_legend(reverse = TRUE) )
ggplot(fig.meta, aes(x = bac.host, y = prob, color = lifestyle)) +
geom_point(size = 3, position = pd) + ylim(0,0.15)+
geom_errorbar(aes(ymin = asymp.LCL, ymax = asymp.UCL),
width = 0.2, position = pd)+
ylab("Probability of 1 or more ParS \n binding sites in phage genome") +
xlab("Bacterial Host") +
labs(color = "Phage Lifestyle") +
theme_classic(base_size = 14) +
guides(colour = guide_legend(reverse = FALSE))
## create binary list of phages w/ and w/out ParS hits
ParS.sum <- all %>% group_by(Accession, host_phage_spor) %>%
summarise(total.ParS.hits = sum(hit), pos.ParS.hit = max(hit), .groups = "drop") #%>%
ParS.pos$host_phage_spor <- factor(ParS.pos$host_phage_spor, levels = c('Bacillota_Lytic_Spor', 'Bacillota_Temp_Spor', 'Bacillota_Lytic_NonSpor', 'Bacillota_Temp_NonSpor', 'OtherPhyla_Lytic_NonSpor', 'OtherPhyla_Temp_NonSpor'), ordered = TRUE)
only.pos <- subset(ParS.pos, ParS.pos$total.ParS.hits>0)
ggplot(ParS.pos, aes(x = factor(host_phage_spor), fill = factor(host_phage_spor), color=factor(host_phage_spor), y = total.ParS.hits )) + geom_boxplot(binaxis = "y", stackdir = "center", position = "dodge") + geom_jitter(width = 0.2, size=0.1) + ylab("Number of ParS sites / Genome") +theme(axis.text.x = element_text(angle = 50, vjust = 1, hjust = 1)) + theme(legend.position = "left") + xlab("Phyla_Spor_Lifestyle") + ggtitle("Total ParS Binding Sites per Genome") ##+ scale_x_discrete(labels = c('Bacillota_Lytic_Spor' = 'Lytic', 'Bacillota_Temp_Spor' = 'Temperate', 'Bacillota_Lytic_NonSpor' = 'Lytic', 'Bacillota_Temp_NonSpor' = 'Temperate', 'OtherPhyla_Lytic_NonSpor'= 'Lytic','OtherPhyla_Temp_NonSpor' = 'Temperate' ))
ggplot(only.pos, aes(x = factor(host_phage_spor), fill = factor(host_phage_spor), color=factor(host_phage_spor), y = total.ParS.hits )) + geom_violin(binaxis = "y", stackdir = "center", position = "dodge") + geom_jitter(width = 0.2, size=1) + ylab("Total Number of ParS sites in Phages with at least 1 or More ParS Site") +theme(axis.text.x = element_text(angle = 50, vjust = 1, hjust = 1)) + theme(legend.position = "left") + xlab("Phyla_Spor_Lifestyle") + ggtitle("Total ParS Binding Sites per Genome") ##+ scale_x_discrete(labels = c('Bacillota_Lytic_Spor' = 'Lytic', 'Bacillota_Temp_Spor' = 'Temperate', 'Bacillota_Lytic_NonSpor' = 'Lytic', 'Bacillota_Temp_NonSpor' = 'Temperate', 'OtherPhyla_Lytic_NonSpor'= 'Lytic','OtherPhyla_Temp_NonSpor' = 'Temperate' ))
ggplot(ParS.pos, aes(x = factor(host_phage_spor), fill = factor(host_phage_spor), color=factor(host_phage_spor), y = total.ParS.hits )) + geom_violin() #+ geom_jitter(width = 0.2) + ylab("Number of ParS sites / Genome") +theme(axis.text.x = element_text(angle = 50, vjust = 1, hjust = 1)) + theme(legend.position = "left") + xlab("Phyla_Spor_Lifestyle") + ggtitle("Total ParS Binding Sites per Genome")
ParS_summary <- ParS.pos %>%
group_by(host_phage_spor) %>%
summarise(mean_hit = mean(pos.ParS.hit, na.rm = TRUE)*100,
se_hit = (sd(pos.ParS.hit, na.rm = TRUE)/sqrt(n())))
ParS_summary$se_hit <- ParS_summary$se_hit*100
ggplot(ParS_summary, aes(x = factor(host_phage_spor),
y = mean_hit,
fill = factor(host_phage_spor))) +
geom_col(color = "black", position = "dodge") +
geom_errorbar(aes(ymin = mean_hit - se_hit,
ymax = mean_hit + se_hit),
width = 0.2) +
ylab("% Phage with 1+ ParS site") +
xlab("Phyla_Spor_Lifestyle") +
theme(axis.text.x = element_text(angle = 50, vjust = 1, hjust = 1),
legend.position = "none") + ggtitle("Proportion of Phages with at least 1 ParS Binding Site")
ggplot(ParS.pos, aes(x = factor(host_phage_spor), fill = factor(host_phage_spor), color=factor(host_phage_spor), y = pos.ParS.hit )) +
geom_boxplot(binaxis = "y", stackdir = "center", position = "dodge") + geom_jitter(width = 0.2) + ylab("Number of ParS sites / Genome") +theme(axis.text.x = element_text(angle = 50, vjust = 1, hjust = 1)) + theme(legend.position = "left") + xlab("Phyla_Spor_Lifestyle") + ggtitle("Total ParS Binding Sites per Genome")
library(ggplot2)
library(dplyr)
library(grid)
library(ggpubr)
pd <- position_jitter(width = 0.20, height = 0.10)
# -----------------------------
# Factor levels
levels_order <- c(
'Bacillota_Lytic_Spor',
'Bacillota_Temp_Spor',
'Bacillota_Lytic_NonSpor',
'Bacillota_Temp_NonSpor',
'OtherPhyla_Lytic_NonSpor',
'OtherPhyla_Temp_NonSpor'
)
df_all <- ParS.pos
df_violin <- ParS.pos %>% filter(pos.ParS.hit > 0)
df_all$host_phage_spor <- factor(df_all$host_phage_spor, levels = levels_order, ordered = TRUE)
df_violin$host_phage_spor <- factor(df_violin$host_phage_spor, levels = levels_order, ordered = TRUE)
# -----------------------------
# Total n per group
n_counts <- df_all %>%
group_by(host_phage_spor) %>%
summarise(n = n(), .groups = "drop")
# -----------------------------
# Base violin plot
vp <- ggplot(df_all, aes(x = host_phage_spor, y = total.ParS.hits, color = host_phage_spor)) +
geom_violin(
data = df_violin,
inherit.aes = TRUE,
color = "black",
fill = NA,
scale = "width",
width = 0.8,
trim = TRUE
) +
geom_point(
aes(fill = host_phage_spor),
position = pd,
size = 2,
alpha = 0.5,
shape = 21,
color = "black",
stroke = 0.3
) +
scale_x_discrete(
limits = levels_order, drop = FALSE,
labels = c(
'Bacillota_Lytic_Spor' = 'Lytic',
'Bacillota_Temp_Spor' = 'Temperate',
'Bacillota_Lytic_NonSpor' = 'Lytic',
'Bacillota_Temp_NonSpor' = 'Temperate',
'OtherPhyla_Lytic_NonSpor'= 'Lytic',
'OtherPhyla_Temp_NonSpor' = 'Temperate'
)
) +
coord_cartesian(clip = "off") +
theme_classic() +
theme(
legend.position = "none",
plot.margin = margin(10, 10, 30, 10) # bottom margin for labels
) +
xlab("") +
ylab("Number of ParS Binding Sites per Phage")+ font_sizes + scale_y_continuous(breaks = c(0, 2, 4, 6, 8)) + scale_color_manual(values=(pal7))+ scale_fill_manual(values=(pal7))
# -----------------------------
# Subcategory labels
spor_label <- textGrob("Sporulating Bacillota Host", gp = gpar(fontsize = 14, fontface = "bold"), x = 1/6, y = -0.08, just = "center")
nonspor_label <- textGrob("Non-Sporulating Bacillota Host", gp = gpar(fontsize = 14, fontface = "bold"), x = 3/6, y = -0.08, just = "center")
other_label <- textGrob("Non-Bacillota Host", gp = gpar(fontsize = 14, fontface = "bold"), x = 5/6, y = -0.08, just = "center")
vp_blank <- vp +
annotation_custom(spor_label) +
annotation_custom(nonspor_label) +
annotation_custom(other_label)
# -----------------------------
# n labels under x-axis
# vp_blank <- vp +
# geom_text(
# data = n_counts,
# aes(
# x = host_phage_spor,
# y = -0.5, # below x-axis
# label = paste0("n=", n)
# ),
# inherit.aes = FALSE,
# size = 4
# )
# -----------------------------
# Global Kruskal-Wallis test
#vp <- vp +
# stat_compare_means(
# method = "kruskal.test",
# label.y = max(df_all$total.ParS.hits, na.rm = TRUE) + 2
# )
# -----------------------------
# Pairwise Wilcoxon comparisons
comparisons_list <- list(
c("OtherPhyla_Lytic_NonSpor", "OtherPhyla_Temp_NonSpor"),
c("Bacillota_Lytic_NonSpor", "Bacillota_Temp_NonSpor"),
c("Bacillota_Temp_Spor", "Bacillota_Temp_NonSpor"),
c("Bacillota_Lytic_Spor", "OtherPhyla_Lytic_NonSpor"),
c("Bacillota_Lytic_Spor", "Bacillota_Lytic_NonSpor"),
c("Bacillota_Lytic_Spor", "Bacillota_Temp_Spor")
)
# Compute pairwise p-values with ascending y positions
y_start <- max(df_all$total.ParS.hits, na.rm = TRUE) + 1
y_step <- 1
pairwise_res <- lapply(seq_along(comparisons_list), function(i){
comp <- comparisons_list[[i]]
x <- df_all$total.ParS.hits[df_all$host_phage_spor == comp[1]]
y <- df_all$total.ParS.hits[df_all$host_phage_spor == comp[2]]
wt <- wilcox.test(x, y)
data.frame(
group1 = comp[1],
group2 = comp[2],
p = wt$p.value,
y.position = y_start + (i - 1) * y_step # ascending heights
)
}) %>% bind_rows()
pairwise_res <- pairwise_res %>%
mutate(
p_label = case_when(
p < 0.0001 ~ "p < 0.0001",
p < 0.05 ~ paste0("p = ", signif(p, 1)), # round to 3 significant digits
TRUE ~ "ns"
)
)
# Add to plot
vp <- vp_blank +
stat_pvalue_manual(
pairwise_res,
label = "p_label",
hide.ns = FALSE,
tip.length = 0.02,
size = 4
)
vp
ggsave(here("02_motifs/ParS/figs/deschit_graphs_1e4_kruskawlwall.png"), width = 10, height = 6)

library(dplyr)
library(ggplot2)
library(emmeans)
library(ggsignif)
# -----------------------------
# 1️⃣ Prepare plotting dataframe
prob_df <- as.data.frame(res$probabilities)
fig.meta <- merge(prob_df, meta.cats, by = "host_phage_spor", all = TRUE)
# Set custom x-axis order
fig.meta$host_phage_spor <- factor(
fig.meta$host_phage_spor,
levels = c(
'Bacillota_Lytic_Spor', 'Bacillota_Temp_Spor',
'Bacillota_Lytic_NonSpor', 'Bacillota_Temp_NonSpor',
'OtherPhyla_Lytic_NonSpor', 'OtherPhyla_Temp_NonSpor'
),
ordered = TRUE
)
# -----------------------------
# 2️⃣ Extract pairwise Tukey tests
pairs_df <- as.data.frame(res$pairwise_tests) %>% mutate(
contrast_char = as.character(contrast), p_label = case_when( p.value
< 0.001 ~ “”, p.value < 0.01 ~ ””, p.value
< 0.05 ~ ””, TRUE ~ “ns” ), group1 =
sapply(strsplit(contrast_char, ” / “), [, 1), group2 =
sapply(strsplit(contrast_char,” / “), [, 2) )
## create binary list of phages w/ and w/out ParS hits
ParS.sum <- all %>% group_by(Accession, host_phage_spor) %>%
summarise(total.ParS.hits = sum(hit), pos.ParS.hit = max(hit), .groups = "drop") #%>%
ParS.pos$host_phage_spor <- factor(ParS.pos$host_phage_spor, levels = c('Bacillota_Lytic_Spor', 'Bacillota_Temp_Spor', 'Bacillota_Lytic_NonSpor', 'Bacillota_Temp_NonSpor', 'OtherPhyla_Lytic_NonSpor', 'OtherPhyla_Temp_NonSpor'), ordered = TRUE)
only.pos <- subset(ParS.pos, ParS.pos$total.ParS.hits>0)
####
ggplot(ParS.pos, aes(x = factor(host_phage_spor), fill = factor(host_phage_spor), color=factor(host_phage_spor), y = total.ParS.hits )) + geom_boxplot(binaxis = "y", stackdir = "center", position = "dodge") + geom_jitter(width = 0.2, size=0.1) + ylab("Number of ParS sites / Genome") +theme(axis.text.x = element_text(angle = 50, vjust = 1, hjust = 1)) + theme(legend.position = "left") + xlab("Phyla_Spor_Lifestyle") + ggtitle("Total ParS Binding Sites per Genome") ##+ scale_x_discrete(labels = c('Bacillota_Lytic_Spor' = 'Lytic', 'Bacillota_Temp_Spor' = 'Temperate', 'Bacillota_Lytic_NonSpor' = 'Lytic', 'Bacillota_Temp_NonSpor' = 'Temperate', 'OtherPhyla_Lytic_NonSpor'= 'Lytic','OtherPhyla_Temp_NonSpor' = 'Temperate' ))
Warning: Ignoring unknown parameters: `binaxis` and `stackdir`

ggplot(ParS.pos, aes(x = factor(host_phage_spor), fill = factor(host_phage_spor), color=factor(host_phage_spor), y = pos.ParS.hit )) + geom_violin() #geom_boxplot(binaxis = "y", stackdir = "center", position = "dodge") + geom_jitter(width = 0.2, size=0.1) + ylab("Number of ParS sites / Genome") +theme(axis.text.x = element_text(angle = 50, vjust = 1, hjust = 1)) + theme(legend.position = "left") + xlab("Phyla_Spor_Lifestyle") + ggtitle("Total ParS Binding Sites per Genome") ##+ scale_x_discrete(labels = c('Bacillota_Lytic_Spor' = 'Lytic', 'Bacillota_Temp_Spor' = 'Temperate', 'Bacillota_Lytic_NonSpor' = 'Lytic', 'Bacillota_Temp_NonSpor' = 'Temperate', 'OtherPhyla_Lytic_NonSpor'= 'Lytic','OtherPhyla_Temp_NonSpor' = 'Temperate' ))

ggplot(ParS.pos, aes(x = factor(host_phage_spor), fill = factor(host_phage_spor), color=factor(host_phage_spor), y = pos.ParS.hit )) + geom_violin() + geom_jitter(width = 0.5, height=0.1, size=1) + ylab("Number of ParS sites / Genome") +theme(axis.text.x = element_text(angle = 50, vjust = 1, hjust = 1)) + theme(legend.position = "left") + xlab("Phyla_Spor_Lifestyle") + ggtitle("Total ParS Binding Sites per Genome") ##+ scale_x_discrete(labels = c('Bacillota_Lytic_Spor' = 'Lytic', 'Bacillota_Temp_Spor' = 'Temperate', 'Bacillota_Lytic_NonSpor' = 'Lytic', 'Bacillota_Temp_NonSpor' = 'Temperate', 'OtherPhyla_Lytic_NonSpor'= 'Lytic','OtherPhyla_Temp_NonSpor' = 'Temperate' ))

ggplot(ParS.pos, aes(x = factor(host_phage_spor), fill = factor(host_phage_spor), color=factor(host_phage_spor), y = total.ParS.hits )) + geom_violin() #geom_boxplot(binaxis = "y", stackdir = "center", position = "dodge") + geom_jitter(width = 0.2, size=0.1) + ylab("Number of ParS sites / Genome") +theme(axis.text.x = element_text(angle = 50, vjust = 1, hjust = 1)) + theme(legend.position = "left") + xlab("Phyla_Spor_Lifestyle") + ggtitle("Total ParS Binding Sites per Genome") ##+ scale_x_discrete(labels = c('Bacillota_Lytic_Spor' = 'Lytic', 'Bacillota_Temp_Spor' = 'Temperate', 'Bacillota_Lytic_NonSpor' = 'Lytic', 'Bacillota_Temp_NonSpor' = 'Temperate', 'OtherPhyla_Lytic_NonSpor'= 'Lytic','OtherPhyla_Temp_NonSpor' = 'Temperate' ))
ggsave(here("02_motifs/ParS/default_violin.png"))
Saving 7.29 x 4.51 in image

ggplot(ParS.pos, aes(x = factor(host_phage_spor), fill = factor(host_phage_spor), color=factor(host_phage_spor), y = total.ParS.hits )) + geom_violin() + geom_jitter(width = 0.2, size=1) + ylab("Number of ParS sites / Genome") +theme(axis.text.x = element_text(angle = 50, vjust = 1, hjust = 1)) + theme(legend.position = "left") + xlab("Phyla_Spor_Lifestyle") + ggtitle("Total ParS Binding Sites per Genome") ##+ scale_x_discrete(labels = c('Bacillota_Lytic_Spor' = 'Lytic', 'Bacillota_Temp_Spor' = 'Temperate', 'Bacillota_Lytic_NonSpor' = 'Lytic', 'Bacillota_Temp_NonSpor' = 'Temperate', 'OtherPhyla_Lytic_NonSpor'= 'Lytic','OtherPhyla_Temp_NonSpor' = 'Temperate' ))
ggsave(here("02_motifs/ParS/default_violin_jitter.png"))
Saving 7.29 x 4.51 in image

tplot
tplot_final2
ggsave(here("02_motifs/ParS/figs/signficance_prob_all.png"), width = 10, height = 10)
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQpgYGB7cn0KbGlicmFyeShkcGx5cikKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGdncHVicikKbGlicmFyeShlbW1lYW5zKQpsaWJyYXJ5KGdnc2lnbmlmKQpsaWJyYXJ5KGhlcmUpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGdyaWQpCgpoZXJlOjpoZXJlKCkKCiMjIyBub3RlIHRoZSBxdWFydHogc2NyaXB0IG5lZWRzIHRvIGJlIGZpeGVkLCB0aGUgaGVhZGVycyBhcmUgamFua3kgYW5kIHdyb25nIChoYWQgdG8gYmUgbWFudWFsbHkgZml4ZWQpCgoKClBhclMgPC0gcmVhZC5kZWxpbTIoaGVyZSgiMDJfbW90aWZzL1BhclMvZGF0YS8wMV9vdXRfMDFfaW5kaXZfaG1tMV9QYXJTLnRzdiIpKQoKCgoKIyMgcmVtb3ZlIHRleHQgaGVhZGVycwojUGFyUyA8LSBzdWJzZXQoUGFyUywgUGFyUyRtb3RpZl9pZD09IlBhclNfQlN1YiIpClBhclMkcS52YWx1ZTwtIGFzLm51bWVyaWMoUGFyUyRxLnZhbHVlKQpQYXJTJHAudmFsdWU8LSBhcy5udW1lcmljKFBhclMkcC52YWx1ZSkKUGFyUyRzY29yZTwtIGFzLm51bWVyaWMoUGFyUyRzY29yZSkKCgoKIyMjIHJlbW92ZSBhbnkgZHVwbGljYXRlIG1vdGlmcyAob2NjYXNpb25hbGx5IGEgbW90aWYgd2lsbCBiZSBhIHBhbGluZHJvbWljIGFuZCBoaXQgYm90aCArIGFuZCAtIHN0cmFuZHMpIApQYXJTIDwtIFBhclMgJT4lCiAgZ3JvdXBfYnkoc2VxdWVuY2VfbmFtZSwgc3RhcnQsIHN0b3ApICU+JSAgIyBncm91cCBieSBjb29yZGluYXRlcwogIHNsaWNlX21heChvcmRlcl9ieSA9IHNjb3JlLCBuID0gMSkgJT4lICAgICMga2VlcCBvbmx5IHRoZSByb3cgd2l0aCBoaWdoZXN0IHNjb3JlCiAgdW5ncm91cCgpCiMjIyBwaGkzVCBLWTAzMDc4Miwgc3BiZXRhIEFGMDIwNzEzCgojIyByZWRyb2NrIChhY3Rpbm8gcGhhZ2Ugd2l0aCBQYXJBQlMpIEdVMzM5NDY3LCB1c2VzIHBhclMgc2l0ZXMgYnV0IG9mIGEgZGlmZmVyZW50IHR5cGUgdGhhbiBzcG9ydWxhdGlvbiBQYXJTCgojIyBjb21iaW5lIHBoYWdlIG1ldGFkYXRhIHdpdGggUGFyUyBoaXRzLiBMYWJlbCBhbnkgcGhhZ2UgdGhhdCBoYWQgbm8gUGFyUyBoaXRzIHdpdGggIm5vX2hpdCIgaW4gbWV0YWRhdGEKaW5waCA8LSByZWFkLmNzdihoZXJlKCIwMF9kYXRhIiwgImlucGhhcmVkX2RiIiwgIjE0QXByMjAyNV9rbm93bnNwb3Jlc3RhdHVzLmNzdiIpLCByb3cubmFtZXMgPSAxKQppbnBoJGxpZmVzdHlsZSA8LSBpZmVsc2UoaW5waCRsaWZlc3R5bGU9PSJUZW1wIiwgIlRlbXBlcmF0ZSIsIGlucGgkbGlmZXN0eWxlKQppbnBoJGJhYy5ob3N0IDwtIGlmZWxzZShpbnBoJHNwb3J1bGF0aW9uPT0iU3BvciIsICJTcG9ydWxhdGluZyBCYWNpbGxvdGEiLCAiTm9uc3BvcnVsYXRpbmcgQmFjaWxsb3RhIikgICMiQXNwb3JvZ2Vub3VzIEJhY2lsbG90YSIpCmlucGgkYmFjLmhvc3QgPC0gaWZlbHNlKGlucGgkbmV3Z3RkYl9QaHlsdW09PSJCYWNpbGxvdGEiLCBpbnBoJGJhYy5ob3N0LCAiTm9uLUJhY2lsbG90YSIpCgppbnBoIDwtIHVuaXRlKGlucGgsIG5pY2UsIGMoImxpZmVzdHlsZSIsICJiYWMuaG9zdCIpLCBzZXA9IiBQaGFnZXMgb2YgIiwgcmVtb3ZlID0gRkFMU0UgKQoKCm1ldGEuY2F0cyA8LSB1bmlxdWUoc2VsZWN0KGlucGgsIGhvc3RfcGhhZ2Vfc3BvciwgYmFjLmhvc3QsIGxpZmVzdHlsZSwgbmljZSkpCgoKCiNpbnBoIDwtIHNlbGVjdChpbnBoLCBBY2Nlc3Npb24sIEhvc3QsIEdlbm9tZS5MZW5ndGguLmJwLiwgZ3RkYl9mLCBmX3Nwb3IsIG5ld2d0ZGJfUGh5bHVtLCAgaG9zdF9waGFnZV9zcG9yLCBwaGFnZV90eXBlLCBzcG9ydWxhdGlvbiwgbGlmZXN0eWxlLCBuaWNlKQphbGwgPC0gbWVyZ2UoaW5waCwgUGFyUywgYnkueD0iQWNjZXNzaW9uIiwgYnkueT0ic2VxdWVuY2VfbmFtZSIsIGFsbC54PVRSVUUsIGFsbC55PUZBTFNFKQphbGwkbW90aWZfaWRbaXMubmEoYWxsJG1vdGlmX2lkKV0gPC0gIm5vX2hpdCIKCiMjIyBjcmVhdGUgYmluYXJ5IGhpdCBjb2x1bW4gb2YgMSBmb3IgUGFyUyBoaXQsIDAgaWYgbm8gaGl0CmFsbCRoaXQgPC0gaWZlbHNlKGFsbCRtb3RpZl9pZD09IlBhclNfQlN1YiIsIDEsIDApCmFsbCRxLnZhbHVlW2lzLm5hKGFsbCRxLnZhbHVlKV0gPC0gMQoKIyMgc3Vic2V0IGZvciBKVVNUIEJBQ0lMTFVTIHNpbmNlIGkgdXNlZCBqdXN0IGEgYmFjaWxsdXMgcGFyUyBnZW5lCiNhbGwgPC0gc3Vic2V0KGFsbCwgYWxsJEhvc3Q9PSJCYWNpbGx1cyIpCiNhbGwgPC0gc3Vic2V0KGFsbCwgYWxsJG5ld2d0ZGJfUGh5bHVtID09IkJhY2lsbG90YSIpCgoKCgoKCmBgYAoKCmBgYHtyfQoKCiMjIyB0aHJlc2hvbGQgdGVzdGluZwoKbGlicmFyeShkcGx5cikKbGlicmFyeShnZ3Bsb3QyKQoKIyBEZWZpbmUgdGhyZXNob2xkcwp0aHJlc2hvbGRzIDwtIDEwXnNlcSgtNiwgLTEsIGJ5ID0gMSkgICAjIGZyb20gMWUtNiB0byAxZS0xCnRocmVzaG9sZHMgPC0gYyh0aHJlc2hvbGRzLCAwLjA1LCAxKSAgICAgICMgYWRkIDAuMDUgZXhwbGljaXRseSBpZiBkZXNpcmVkCnJlc3VsdHMgPC0gbGFwcGx5KHRocmVzaG9sZHMsIGZ1bmN0aW9uKHRoKSB7CiAgYWxsICU+JQogICAgZ3JvdXBfYnkoQWNjZXNzaW9uLCBob3N0X3BoYWdlX3Nwb3IpICU+JQogICAgc3VtbWFyaXNlKAogICAgICBwb3MuUGFyUy5oaXQgPSBtYXgoaWZlbHNlKCFpcy5uYShxLnZhbHVlKSAmIHEudmFsdWUgPD0gdGgsIDEsIDApKSwKICAgICAgLmdyb3VwcyA9ICJkcm9wIgogICAgKSAlPiUKICAgIGdyb3VwX2J5KGhvc3RfcGhhZ2Vfc3BvcikgJT4lCiAgICBzdW1tYXJpc2UoCiAgICAgIFBoYWdlX2hhc19QYXJTID0gc3VtKHBvcy5QYXJTLmhpdCksCiAgICAgIHRvdGFsLnBoYWdlID0gbigpLAogICAgICAuZ3JvdXBzID0gImRyb3AiCiAgICApICU+JQogICAgbXV0YXRlKAogICAgICBQYXJTLnBvcy5wZXJjID0gUGhhZ2VfaGFzX1BhclMgLyB0b3RhbC5waGFnZSAqIDEwMCwKICAgICAgdGhyZXNob2xkID0gdGgKICAgICkKfSkgJT4lIGJpbmRfcm93cygpCgpyZXN1bHRzLmZpZyA8LSBtZXJnZShyZXN1bHRzLCBtZXRhLmNhdHMsIGJ5PSJob3N0X3BoYWdlX3Nwb3IiKQoKIyBQbG90CnA8LSBnZ3Bsb3QocmVzdWx0cy5maWcsIGFlcyh4ID0gdGhyZXNob2xkLCB5ID0gUGFyUy5wb3MucGVyYywgY29sb3IgPSBiYWMuaG9zdCwgbGluZXR5cGUgPSBsaWZlc3R5bGUpKSArCiAgZ2VvbV9saW5lKHNpemUgPSAxLjIpICsKICBnZW9tX3BvaW50KCkgKwogIHNjYWxlX3hfbG9nMTAoKSArICAjIGxvZyBzY2FsZSBmb3IgcS12YWx1ZXMKICBsYWJzKAogICAgeCA9ICJGYWxzZSBQb3NpdGl2ZSAocS12YWx1ZSkgVGhyZXNob2xkIiwKICAgIHkgPSAiJSBQaGFnZXMgd2l0aCAxIG9yIG1vcmUgUGFyUyBCaW5kaW5nIFNpdGUiCiAgKSArCiAgdGhlbWVfY2xhc3NpYygpICsgbGFicyhsaW5ldHlwZSA9ICJQaGFnZSBMaWZlc3R5bGUiLCBjb2xvcj0iQmFjdGVyaWFsIEhvc3QiKSArZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMWUtNCwgbGluZXR5cGUgPSAiZG90dGVkIikgIysgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikKcCArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9IGMoMC4yLCAwLjcpKQoKCgpnZ3NhdmUoaGVyZSgiMDJfbW90aWZzL1BhclMvZmlncy9xX3RocmVzaG9sZC5wbmciKSwgd2lkdGggPSA3LCBoZWlnaHQgPSA1KQpgYGAKCgpgYGB7cn0KCiMjIyMgUSBGSUxURVJJTkcKUGFyUy5xdWFsIDwtIGFsbAoKI1BhclMucXVhbCRoaXQgPC0gaWZlbHNlKFBhclMucXVhbCRxLnZhbHVlPDFlLTQsIDEsIDApClBhclMucXVhbCRoaXQgPC0gaWZlbHNlKFBhclMucXVhbCRxLnZhbHVlPDFlLTQsIDEsIDApCgojIyBjcmVhdGUgYmluYXJ5IGxpc3Qgb2YgcGhhZ2VzIHcvIGFuZCB3L291dCBQYXJTIGhpdHMKUGFyUy5wb3MgPC0gUGFyUy5xdWFsICU+JSBncm91cF9ieShBY2Nlc3Npb24sIGhvc3RfcGhhZ2Vfc3BvcikgJT4lCiAgc3VtbWFyaXNlKHRvdGFsLlBhclMuaGl0cyA9IHN1bShoaXQpLCBwb3MuUGFyUy5oaXQgPSBtYXgoaGl0KSwgLmdyb3VwcyA9ICJkcm9wIikgIyU+JSAKUGFyUy5wb3MgJT4lIGNvdW50KGhvc3RfcGhhZ2Vfc3BvcikKCiMjIGNyZWF0ZSBiaW5hcnkgbGlzdCBvZiBwaGFnZXMgdy8gYW5kIHcvb3V0IFBhclMgaGl0cwpQYXJTLnNhbml0eSA8LSBQYXJTLnF1YWwgJT4lIGdyb3VwX2J5KEFjY2Vzc2lvbiwgaG9zdF9waGFnZV9zcG9yLCBIb3N0LCBEZXNjcmlwdGlvbiwgTG93ZXN0LlRheGEsIEdlbnVzLCBGYW1pbHksIEdlbm9tZS5MZW5ndGguLmJwLiwgbW9sR0MuLi4uKSAlPiUKICBzdW1tYXJpc2UodG90YWwuUGFyUy5oaXRzID0gc3VtKGhpdCksIHBvcy5QYXJTLmhpdCA9IG1heChoaXQpLCAuZ3JvdXBzID0gImRyb3AiKSAjJT4lIAoKb25seS5wb3MgPC0gc3Vic2V0KFBhclMuc2FuaXR5LCBQYXJTLnNhbml0eSR0b3RhbC5QYXJTLmhpdHM+MCkKCgpQYXJTLnBvcyAlPiUgY291bnQoaG9zdF9waGFnZV9zcG9yKQoKUGFyUy5zdW1tYXJ5IDwtIFBhclMucG9zICU+JSAKICBncm91cF9ieShob3N0X3BoYWdlX3Nwb3IpICU+JSAKICBzdW1tYXJpc2UoCiAgICB0b3RhbF9QYXJTX2hpdHMgPSBzdW0odG90YWwuUGFyUy5oaXRzLCBuYS5ybSA9IFRSVUUpLAogICAgUGhhZ2VfaGFzX1BhclMgPSBzdW0ocG9zLlBhclMuaGl0LCBuYS5ybSA9IFRSVUUpLAogICAgdG90YWwucGhhZ2UgPSBuKCksCiAgICAuZ3JvdXBzID0gImRyb3AiCiAgKSAlPiUgCiAgbXV0YXRlKFBhclMucG9zLnBlcmMgPSBQaGFnZV9oYXNfUGFyUyAvIHRvdGFsLnBoYWdlICogMTAwKQoKCgoKYGBgCgoKCgoKCgoKYGBge3J9CgoKI1BhclMuaGl0cyA8LSBQYXJTLmhpdHMuYWxsClBhclMuaGl0cyA8LSBzdWJzZXQoUGFyUy5xdWFsLCBQYXJTLnF1YWwkaGl0PT0xKQojUGFyUy5oaXRzIDwtIHN1YnNldChQYXJTLmhpdHMuYWxsLCBQYXJTLmhpdHMuYWxsJHBoYWdlX3R5cGU9PSJMeXRpY19TcG9yIikKCiMjIGZpbmQgY2VudGVyIHBoYWdlIGdlbm9tZSAod2hvbGUgZ2Vub21lIC8yKQpQYXJTLmhpdHMkc2VxLm1kcHQgPC0gYXMubnVtZXJpYyhQYXJTLmhpdHMkR2Vub21lLkxlbmd0aC4uYnAuKS8yCgojIyBmaW5kIGNlbnRlciBvZiBtb3RpZiAKUGFyUy5oaXRzJG1vdGlmLm1kcHQgPC0gKFBhclMuaGl0cyRzdG9wICsgUGFyUy5oaXRzJHN0YXJ0ICkvIDIKCiMjIyBzdWJ0cmFjdCBtaWRwb2ludCBtb3RpZiBmcm9tIHNlcXVlbmNlIG1pZHBvaW50IHRvIHNlZSBob3cgZmFyIGF3YXkgdGhleSBhcmUKUGFyUy5oaXRzIDwtIFBhclMuaGl0cyAlPiUKICBtdXRhdGUobWRwdC5hbGlnbiA9IChtb3RpZi5tZHB0IC0gc2VxLm1kcHQpKQoKUGFyUy5oaXRzJG1kcHQuYWxpZ24ua2JwIDwtIFBhclMuaGl0cyRtZHB0LmFsaWduLzEwMDAKCiMjIyMgVE8gR0VUIFJFTEFUSVZFIG1vdGlmIGFsaWdubWVuCgojIFJlbGF0aXZlIHBvc2l0aW9uIGFzIGZyYWN0aW9uIG9mIGdlbm9tZSBsZW5ndGgKIyAoLTAuNSA9IHN0YXJ0LCAwID0gY2VudGVyLCArMC41ID0gZW5kKQpQYXJTLmhpdHMkcmVsLm1kcHQgPC0gKFBhclMuaGl0cyRtb3RpZi5tZHB0IC0gUGFyUy5oaXRzJHNlcS5tZHB0KSAvIFBhclMuaGl0cyRHZW5vbWUuTGVuZ3RoLi5icC4KCgoKIyBSZWxhdGl2ZSBwb3NpdGlvbiBmcm9tIGdlbm9tZSBzdGFydCAoMCB0byAxKQpQYXJTLmhpdHMkcmVsLmZyYWMgPC0gUGFyUy5oaXRzJG1vdGlmLm1kcHQgLyBQYXJTLmhpdHMkR2Vub21lLkxlbmd0aC4uYnAuCgojIE9wdGlvbmFsbHkgY29udmVydCB0byBwZXJjZW50YWdlClBhclMuaGl0cyRyZWwucGVyY2VudCA8LSBQYXJTLmhpdHMkcmVsLmZyYWMgKiAxMDAKCiMjU2V0IHNwZWNpZmljIG9yZGVyIGZvciBiYWN0ZXJpYWwgaG9zdHMgdG8gYXBwZWFyIG9uIGdyYXBocwpQYXJTLmhpdHMkbmljZSA8LSBmYWN0b3IoUGFyUy5oaXRzJG5pY2UsIGxldmVscyA9IGMoJ0x5dGljIFBoYWdlcyBvZiBTcG9ydWxhdGluZyBCYWNpbGxvdGEnLCAnVGVtcGVyYXRlIFBoYWdlcyBvZiBTcG9ydWxhdGluZyBCYWNpbGxvdGEnLCAnTHl0aWMgUGhhZ2VzIG9mIE5vbnNwb3J1bGF0aW5nIEJhY2lsbG90YScsICdUZW1wZXJhdGUgUGhhZ2VzIG9mIE5vbnNwb3J1bGF0aW5nIEJhY2lsbG90YScsICdMeXRpYyBQaGFnZXMgb2YgTm9uLUJhY2lsbG90YScsICdUZW1wZXJhdGUgUGhhZ2VzIG9mIE5vbi1CYWNpbGxvdGEnICksb3JkZXJlZCA9IFRSVUUpCgoKZ2dwbG90KFBhclMuaGl0cywgYWVzKHggPSBtZHB0LmFsaWduLmticCwgZmlsbCA9IG5pY2UpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSA1LCBwb3NpdGlvbiA9ICJkb2RnZSIpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAwLCBsaW5ldHlwZSA9ICJkb3R0ZWQiKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgtOTAsIDkwLCBieSA9IDMwKSkgKwogIGxhYnMoeCA9ICJNb3RpZiBkaXN0YW5jZSAoa2IpIGZyb20gY2VudGVyIG9mIHBoYWdlIGdlbm9tZSIsIHkgPSAiTW90aWYgY291bnQiKSArCiAgZmFjZXRfd3JhcCh+IG5pY2UsIG5jb2wgPSAyKSsgZ2d0aXRsZSgiQWJzb2x1dGUgUG9zaXRpb24gb2YgUGFyUyBNb3RpZiBvbiBQaGFnZSBHZW5vbWVzIikgKyB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKQoKZ2dzYXZlKGhlcmUoIjAyX21vdGlmcy9QYXJTL2ZpZ3MvQWJzb2x1dGVQYXJTcG9zaXRpb24ucG5nIikpCgpnZ3Bsb3QoUGFyUy5oaXRzLCBhZXMoeCA9IHJlbC5tZHB0LCBmaWxsID0gbmljZSkpICsKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDAuMDUsIHBvc2l0aW9uID0gImRvZGdlIikgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDAsIGxpbmV0eXBlID0gImRvdHRlZCIpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKC0wLjUsIDAuNSwgYnkgPSAwLjI1KSkgKwogIGxhYnMoeCA9ICJNb3RpZiBwb3NpdGlvbiByZWxhdGl2ZSB0byBjZW50ZXIgb2YgcGhhZ2UgZ2Vub21lIiwgeSA9ICJNb3RpZiBjb3VudCIpICsKICBmYWNldF93cmFwKH4gbmljZSwgbmNvbCA9IDIpICsgZ2d0aXRsZSgiUmVsYXRpdmUgUG9zaXRpb24gb2YgUGFyUyBNb3RpZiBvbiBQaGFnZSBHZW5vbWVzIikrIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpCgpnZ3NhdmUoaGVyZSgiMDJfbW90aWZzL1BhclMvZmlncy9SZWxhdGl2ZVBhclNwb3NpdGlvbi5wbmciKSkKYGBgCnN0YXRpc3RpY3MgCmBgYHtyfQoKUGFyUy5iaW4gPC0gUGFyUy5wb3NbLGMoMSwyLDQpXQoKCgpgYGAKCmBgYHtyfQphbmFseXplX2VucmljaG1lbnQgPC0gZnVuY3Rpb24oZGYsIHJlZl90cmVhdG1lbnQgPSAiRW5yaWNoZWQiKSB7CiAgIyByZWxldmVsIHRoZSB0cmVhdG1lbnQgZmFjdG9yCiAgZGYkaG9zdF9waGFnZV9zcG9yIDwtIHJlbGV2ZWwoZmFjdG9yKGRmJGhvc3RfcGhhZ2Vfc3BvciksIHJlZiA9IHJlZl90cmVhdG1lbnQpCiAgCiAgIyBsb2dpc3RpYyByZWdyZXNzaW9uOiBtb3RpZiBwcmVzZW5jZSB+IHRyZWF0bWVudAogIG1vZGVsIDwtIGdsbShwb3MuUGFyUy5oaXQgfiBob3N0X3BoYWdlX3Nwb3IsIGZhbWlseSA9IGJpbm9taWFsLCBkYXRhID0gZGYpCiAgCiAgIyBlc3RpbWF0ZWQgcHJvYmFiaWxpdGllcyBwZXIgdHJlYXRtZW50CiAgZW1tIDwtIGVtbWVhbnMobW9kZWwsIH4gaG9zdF9waGFnZV9zcG9yLCB0eXBlID0gInJlc3BvbnNlIikKICAKICBsaXN0KAogICAgbW9kZWxfc3VtbWFyeSA9IHN1bW1hcnkobW9kZWwpLAogICAgcHJvYmFiaWxpdGllcyA9IGVtbSwKICAgIHBhaXJ3aXNlX3Rlc3RzID0gcGFpcnMoZW1tLCBhZGp1c3QgPSAidHVrZXkiKQogICkKfQpsaWJyYXJ5KGVtbWVhbnMpCnJlcyA8LSBhbmFseXplX2VucmljaG1lbnQoUGFyUy5iaW4sIHJlZl90cmVhdG1lbnQgPSAiQmFjaWxsb3RhX0x5dGljX1Nwb3IiKQoKcmVzJG1vZGVsX3N1bW1hcnkgICAgICMgbG9naXN0aWMgcmVncmVzc2lvbiBjb2VmZmljaWVudHMKcmVzJHByb2JhYmlsaXRpZXMgICAgICMgZXN0aW1hdGVkIG1vdGlmIHByb2JhYmlsaXR5IHBlciB0cmVhdG1lbnQKcmVzJHBhaXJ3aXNlX3Rlc3RzICAgICMgYWxsIHBhaXJ3aXNlIGNvbXBhcmlzb25zCgoKcHJvYl9kZiA8LSBhcy5kYXRhLmZyYW1lKHJlcyRwcm9iYWJpbGl0aWVzKQpoZWFkKHByb2JfZGYpCgpwcm9iX2RmJGhvc3RfcGhhZ2Vfc3BvciA8LSByZW9yZGVyKHByb2JfZGYkaG9zdF9waGFnZV9zcG9yLCBwcm9iX2RmJHByb2IpCgoKCmZpZy5tZXRhIDwtIG1lcmdlKHByb2JfZGYsIG1ldGEuY2F0cywgYnk9Imhvc3RfcGhhZ2Vfc3BvciIsIGFsbD1UUlVFKQoKCgpgYGAKCgoKYGBge3J9CnBkIDwtIHBvc2l0aW9uX2RvZGdlKHdpZHRoID0gMC42KSAgIyBhZGp1c3Qgd2lkdGggYXMgbmVlZGVkCgpnZ3Bsb3QoZmlnLm1ldGEsIGFlcyh4ID0gbGlmZXN0eWxlLCB5ID0gcHJvYiwgY29sb3IgPSBiYWMuaG9zdCkpICsKICBnZW9tX3BvaW50KHNpemUgPSAzLCBwb3NpdGlvbiA9IHBkKSArICB5bGltKDAsMC4yMCkrCiAgZ2VvbV9lcnJvcmJhcihhZXMoeW1pbiA9IGFzeW1wLkxDTCwgeW1heCA9IGFzeW1wLlVDTCksIAogICAgICAgICAgICAgICAgd2lkdGggPSAwLjIsIHBvc2l0aW9uID0gcGQpICsKICB5bGFiKCJQcm9iYWJpbGl0eSBvZiAxIG9yIG1vcmUgUGFyUyBcbiBiaW5kaW5nIHNpdGVzIGluIHBoYWdlIGdlbm9tZSIpICsKICB4bGFiKCJCYWN0ZXJpYWwgSG9zdCIpICsKICBsYWJzKGNvbG9yID0gIlBoYWdlIExpZmVzdHlsZSIpICsKICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZSA9IDE0KSArCiAgZ3VpZGVzKGNvbG91ciA9IGd1aWRlX2xlZ2VuZChyZXZlcnNlID0gRkFMU0UpKQoKCgpnZ3Bsb3QoZmlnLm1ldGEsIGFlcyh4ID0gYmFjLmhvc3QsIHkgPSBwcm9iLCBjb2xvcj1saWZlc3R5bGUpKSArCiAgZ2VvbV9wb2ludChzaXplID0gMykgKyB5bGltKDAsMC4xNSkrCiAgZ2VvbV9lcnJvcmJhcihhZXMoeW1pbiA9IGFzeW1wLkxDTCwgeW1heCA9IGFzeW1wLlVDTCksIHdpZHRoID0gMC4yKSArCiAgeWxhYigiUHJvYmFiaWx0eSBvZiAxIG9yIG1vcmUgUGFyUyBcbiBiaW5kaW5nIHNpdGVzIGluIHBoYWdlIGdlbm9tZSIpICsKICB4bGFiKCJCYWN0ZXJpYWwgSG9zdCIpICsgbGFicyhjb2xvciA9ICJQaGFnZSBMaWZlc3R5bGUiKSArIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTQpICArIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQocmV2ZXJzZT1GKSkKICAjZ2d0aXRsZSgiTW90aWYgZW5yaWNobWVudCBhY3Jvc3MgdHJlYXRtZW50cyIpKyBndWlkZSA9IGd1aWRlX2xlZ2VuZChyZXZlcnNlID0gVFJVRSkgKQoKCgpnZ3Bsb3QoZmlnLm1ldGEsIGFlcyh4ID0gYmFjLmhvc3QsIHkgPSBwcm9iLCBjb2xvciA9IGxpZmVzdHlsZSkpICsKICBnZW9tX3BvaW50KHNpemUgPSAzLCBwb3NpdGlvbiA9IHBkKSArIHlsaW0oMCwwLjE1KSsKICBnZW9tX2Vycm9yYmFyKGFlcyh5bWluID0gYXN5bXAuTENMLCB5bWF4ID0gYXN5bXAuVUNMKSwgCiAgICAgICAgICAgICAgICB3aWR0aCA9IDAuMiwgcG9zaXRpb24gPSBwZCkrCiAgeWxhYigiUHJvYmFiaWxpdHkgb2YgMSBvciBtb3JlIFBhclMgXG4gYmluZGluZyBzaXRlcyBpbiBwaGFnZSBnZW5vbWUiKSArCiAgeGxhYigiQmFjdGVyaWFsIEhvc3QiKSArCiAgbGFicyhjb2xvciA9ICJQaGFnZSBMaWZlc3R5bGUiKSArCiAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxNCkgKwogIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQocmV2ZXJzZSA9IEZBTFNFKSkKCmBgYAoKYGBge3J9CgojIyBjcmVhdGUgYmluYXJ5IGxpc3Qgb2YgcGhhZ2VzIHcvIGFuZCB3L291dCBQYXJTIGhpdHMKUGFyUy5zdW0gPC0gYWxsICU+JSBncm91cF9ieShBY2Nlc3Npb24sIGhvc3RfcGhhZ2Vfc3BvcikgJT4lCiAgc3VtbWFyaXNlKHRvdGFsLlBhclMuaGl0cyA9IHN1bShoaXQpLCBwb3MuUGFyUy5oaXQgPSBtYXgoaGl0KSwgLmdyb3VwcyA9ICJkcm9wIikgIyU+JSAKCgpQYXJTLnBvcyRob3N0X3BoYWdlX3Nwb3IgPC0gZmFjdG9yKFBhclMucG9zJGhvc3RfcGhhZ2Vfc3BvciwgbGV2ZWxzID0gYygnQmFjaWxsb3RhX0x5dGljX1Nwb3InLCAnQmFjaWxsb3RhX1RlbXBfU3BvcicsICdCYWNpbGxvdGFfTHl0aWNfTm9uU3BvcicsICdCYWNpbGxvdGFfVGVtcF9Ob25TcG9yJywgJ090aGVyUGh5bGFfTHl0aWNfTm9uU3BvcicsICdPdGhlclBoeWxhX1RlbXBfTm9uU3BvcicpLCBvcmRlcmVkID0gVFJVRSkKCm9ubHkucG9zIDwtIHN1YnNldChQYXJTLnBvcywgUGFyUy5wb3MkdG90YWwuUGFyUy5oaXRzPjApCgoKZ2dwbG90KFBhclMucG9zLCBhZXMoeCA9IGZhY3Rvcihob3N0X3BoYWdlX3Nwb3IpLCBmaWxsID0gZmFjdG9yKGhvc3RfcGhhZ2Vfc3BvciksIGNvbG9yPWZhY3Rvcihob3N0X3BoYWdlX3Nwb3IpLCB5ID0gdG90YWwuUGFyUy5oaXRzICkpICsgZ2VvbV9ib3hwbG90KGJpbmF4aXMgPSAieSIsIHN0YWNrZGlyID0gImNlbnRlciIsIHBvc2l0aW9uID0gImRvZGdlIikgKyAgZ2VvbV9qaXR0ZXIod2lkdGggPSAwLjIsIHNpemU9MC4xKSArIHlsYWIoIk51bWJlciBvZiBQYXJTIHNpdGVzIC8gR2Vub21lIikgK3RoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNTAsIHZqdXN0ID0gMSwgaGp1c3QgPSAxKSkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibGVmdCIpICsgeGxhYigiUGh5bGFfU3Bvcl9MaWZlc3R5bGUiKSArIGdndGl0bGUoIlRvdGFsIFBhclMgQmluZGluZyBTaXRlcyBwZXIgR2Vub21lIikgICMjKyBzY2FsZV94X2Rpc2NyZXRlKGxhYmVscyA9IGMoJ0JhY2lsbG90YV9MeXRpY19TcG9yJyA9ICdMeXRpYycsICdCYWNpbGxvdGFfVGVtcF9TcG9yJyA9ICdUZW1wZXJhdGUnLCAnQmFjaWxsb3RhX0x5dGljX05vblNwb3InID0gJ0x5dGljJywgJ0JhY2lsbG90YV9UZW1wX05vblNwb3InICA9ICdUZW1wZXJhdGUnLCAnT3RoZXJQaHlsYV9MeXRpY19Ob25TcG9yJz0gJ0x5dGljJywnT3RoZXJQaHlsYV9UZW1wX05vblNwb3InID0gJ1RlbXBlcmF0ZScgKSkKCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKZ2dwbG90KG9ubHkucG9zLCBhZXMoeCA9IGZhY3Rvcihob3N0X3BoYWdlX3Nwb3IpLCBmaWxsID0gZmFjdG9yKGhvc3RfcGhhZ2Vfc3BvciksIGNvbG9yPWZhY3Rvcihob3N0X3BoYWdlX3Nwb3IpLCB5ID0gdG90YWwuUGFyUy5oaXRzICkpICsgZ2VvbV92aW9saW4oYmluYXhpcyA9ICJ5Iiwgc3RhY2tkaXIgPSAiY2VudGVyIiwgcG9zaXRpb24gPSAiZG9kZ2UiKSArICBnZW9tX2ppdHRlcih3aWR0aCA9IDAuMiwgc2l6ZT0xKSArIHlsYWIoIlRvdGFsIE51bWJlciBvZiBQYXJTIHNpdGVzIGluIFBoYWdlcyB3aXRoIGF0IGxlYXN0IDEgb3IgTW9yZSBQYXJTIFNpdGUiKSArdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA1MCwgdmp1c3QgPSAxLCBoanVzdCA9IDEpKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJsZWZ0IikgKyB4bGFiKCJQaHlsYV9TcG9yX0xpZmVzdHlsZSIpICsgZ2d0aXRsZSgiVG90YWwgUGFyUyBCaW5kaW5nIFNpdGVzIHBlciBHZW5vbWUiKSAgIyMrIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzID0gYygnQmFjaWxsb3RhX0x5dGljX1Nwb3InID0gJ0x5dGljJywgJ0JhY2lsbG90YV9UZW1wX1Nwb3InID0gJ1RlbXBlcmF0ZScsICdCYWNpbGxvdGFfTHl0aWNfTm9uU3BvcicgPSAnTHl0aWMnLCAnQmFjaWxsb3RhX1RlbXBfTm9uU3BvcicgID0gJ1RlbXBlcmF0ZScsICdPdGhlclBoeWxhX0x5dGljX05vblNwb3InPSAnTHl0aWMnLCdPdGhlclBoeWxhX1RlbXBfTm9uU3BvcicgPSAnVGVtcGVyYXRlJyApKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIApnZ3Bsb3QoUGFyUy5wb3MsIGFlcyh4ID0gZmFjdG9yKGhvc3RfcGhhZ2Vfc3BvciksIGZpbGwgPSBmYWN0b3IoaG9zdF9waGFnZV9zcG9yKSwgY29sb3I9ZmFjdG9yKGhvc3RfcGhhZ2Vfc3BvciksIHkgPSB0b3RhbC5QYXJTLmhpdHMgKSkgKyBnZW9tX3Zpb2xpbigpICMrICBnZW9tX2ppdHRlcih3aWR0aCA9IDAuMikgKyB5bGFiKCJOdW1iZXIgb2YgUGFyUyBzaXRlcyAvIEdlbm9tZSIpICt0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDUwLCB2anVzdCA9IDEsIGhqdXN0ID0gMSkpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImxlZnQiKSArIHhsYWIoIlBoeWxhX1Nwb3JfTGlmZXN0eWxlIikgKyBnZ3RpdGxlKCJUb3RhbCBQYXJTIEJpbmRpbmcgU2l0ZXMgcGVyIEdlbm9tZSIpCgoKClBhclNfc3VtbWFyeSA8LSBQYXJTLnBvcyAlPiUKICBncm91cF9ieShob3N0X3BoYWdlX3Nwb3IpICU+JQogIHN1bW1hcmlzZShtZWFuX2hpdCA9IG1lYW4ocG9zLlBhclMuaGl0LCBuYS5ybSA9IFRSVUUpKjEwMCwKICAgICAgICAgICAgc2VfaGl0ID0gKHNkKHBvcy5QYXJTLmhpdCwgbmEucm0gPSBUUlVFKS9zcXJ0KG4oKSkpKQoKUGFyU19zdW1tYXJ5JHNlX2hpdCA8LSBQYXJTX3N1bW1hcnkkc2VfaGl0KjEwMAoKZ2dwbG90KFBhclNfc3VtbWFyeSwgYWVzKHggPSBmYWN0b3IoaG9zdF9waGFnZV9zcG9yKSwKICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSBtZWFuX2hpdCwKICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBmYWN0b3IoaG9zdF9waGFnZV9zcG9yKSkpICsKICBnZW9tX2NvbChjb2xvciA9ICJibGFjayIsIHBvc2l0aW9uID0gImRvZGdlIikgKyAKICBnZW9tX2Vycm9yYmFyKGFlcyh5bWluID0gbWVhbl9oaXQgLSBzZV9oaXQsCiAgICAgICAgICAgICAgICAgICAgeW1heCA9IG1lYW5faGl0ICsgc2VfaGl0KSwKICAgICAgICAgICAgICAgIHdpZHRoID0gMC4yKSArCiAgeWxhYigiJSBQaGFnZSB3aXRoIDErIFBhclMgIHNpdGUiKSArCiAgeGxhYigiUGh5bGFfU3Bvcl9MaWZlc3R5bGUiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA1MCwgdmp1c3QgPSAxLCBoanVzdCA9IDEpLAogICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKyBnZ3RpdGxlKCJQcm9wb3J0aW9uIG9mIFBoYWdlcyB3aXRoIGF0IGxlYXN0IDEgUGFyUyBCaW5kaW5nIFNpdGUiKQoKCgpnZ3Bsb3QoUGFyUy5wb3MsIGFlcyh4ID0gZmFjdG9yKGhvc3RfcGhhZ2Vfc3BvciksIGZpbGwgPSBmYWN0b3IoaG9zdF9waGFnZV9zcG9yKSwgY29sb3I9ZmFjdG9yKGhvc3RfcGhhZ2Vfc3BvciksIHkgPSBwb3MuUGFyUy5oaXQgKSkgKwogIGdlb21fYm94cGxvdChiaW5heGlzID0gInkiLCBzdGFja2RpciA9ICJjZW50ZXIiLCBwb3NpdGlvbiA9ICJkb2RnZSIpICsgIGdlb21faml0dGVyKHdpZHRoID0gMC4yKSArIHlsYWIoIk51bWJlciBvZiBQYXJTIHNpdGVzIC8gR2Vub21lIikgK3RoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNTAsIHZqdXN0ID0gMSwgaGp1c3QgPSAxKSkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibGVmdCIpICsgeGxhYigiUGh5bGFfU3Bvcl9MaWZlc3R5bGUiKSArIGdndGl0bGUoIlRvdGFsIFBhclMgQmluZGluZyBTaXRlcyBwZXIgR2Vub21lIikKCgoKCgpgYGAKCmBgYHtyfQpwYWlyd2lzZV9maXNoZXJfc3VtbWFyeSA8LSBmdW5jdGlvbihkZiwgZ3JvdXBfY29sID0gImdyb3VwIiwgaGl0c19jb2wgPSAiaGl0cyIsIHRvdGFsX2NvbCA9ICJ0b3RhbCIsIHAuYWRqdXN0Lm1ldGhvZCA9ICJCSCIpIHsKICBncm91cHMgPC0gZGZbW2dyb3VwX2NvbF1dCiAgY29tYnMgPC0gY29tYm4oZ3JvdXBzLCAyLCBzaW1wbGlmeSA9IEZBTFNFKQogIAogIHJlc3VsdHMgPC0gZGF0YS5mcmFtZSgKICAgIGdyb3VwMSA9IGNoYXJhY3RlcigpLAogICAgZ3JvdXAyID0gY2hhcmFjdGVyKCksCiAgICBvZGRzX3JhdGlvID0gbnVtZXJpYygpLAogICAgY29uZl9sb3cgPSBudW1lcmljKCksCiAgICBjb25mX2hpZ2ggPSBudW1lcmljKCksCiAgICBwLnZhbHVlID0gbnVtZXJpYygpLAogICAgcC5hZGogPSBudW1lcmljKCksCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKICApCiAgCiAgcHZhbHMgPC0gbnVtZXJpYygpCiAgCiAgZm9yIChjIGluIGNvbWJzKSB7CiAgICAjIHN1YnNldCB0aGUgdHdvIGdyb3VwcwogICAgZzEgPC0gZGZbZGZbW2dyb3VwX2NvbF1dID09IGNbMV0sIF0KICAgIGcyIDwtIGRmW2RmW1tncm91cF9jb2xdXSA9PSBjWzJdLCBdCiAgICAKICAgICMgY3JlYXRlIDJ4MiB0YWJsZQogICAgdGFiIDwtIG1hdHJpeChjKAogICAgICBnMVtbaGl0c19jb2xdXSwgZzFbW3RvdGFsX2NvbF1dIC0gZzFbW2hpdHNfY29sXV0sCiAgICAgIGcyW1toaXRzX2NvbF1dLCBnMltbdG90YWxfY29sXV0gLSBnMltbaGl0c19jb2xdXQogICAgKSwgbnJvdyA9IDIsIGJ5cm93ID0gVFJVRSkKICAgIAogICAgcm93bmFtZXModGFiKSA8LSBjKGNbMV0sIGNbMl0pCiAgICBjb2xuYW1lcyh0YWIpIDwtIGMoIlByZXNlbnQiLCAiQWJzZW50IikKICAgIAogICAgZnQgPC0gZmlzaGVyLnRlc3QodGFiKQogICAgCiAgICBwdmFscyA8LSBjKHB2YWxzLCBmdCRwLnZhbHVlKQogICAgCiAgICByZXN1bHRzIDwtIHJiaW5kKHJlc3VsdHMsIGRhdGEuZnJhbWUoCiAgICAgIGdyb3VwMSA9IGNbMV0sCiAgICAgIGdyb3VwMiA9IGNbMl0sCiAgICAgIG9kZHNfcmF0aW8gPSBmdCRlc3RpbWF0ZSwgICAgICAgICAgICAgIyBvZGRzIHJhdGlvCiAgICAgIGNvbmZfbG93ID0gZnQkY29uZi5pbnRbMV0sICAgICAgICAgICAgIyBsb3dlciA5NSUgQ0kKICAgICAgY29uZl9oaWdoID0gZnQkY29uZi5pbnRbMl0sICAgICAgICAgICAjIHVwcGVyIDk1JSBDSQogICAgICBwLnZhbHVlID0gZnQkcC52YWx1ZSwKICAgICAgcC5hZGogPSBOQQogICAgKSkKICB9CiAgCiAgIyBBZGp1c3QgcC12YWx1ZXMKICByZXN1bHRzJHAuYWRqIDwtIHAuYWRqdXN0KHB2YWxzLCBtZXRob2QgPSBwLmFkanVzdC5tZXRob2QpCiAgCiAgcmV0dXJuKHJlc3VsdHMpCn0KCgojUGFycy5GSVNILCAKCnQgPC0gcGFpcndpc2VfZmlzaGVyX3N1bW1hcnkoCiAgZGYgPSBQYXJTLnN1bW1hcnksCiAgZ3JvdXBfY29sID0gImhvc3RfcGhhZ2Vfc3BvciIsIAogIGhpdHNfY29sID0gIlBoYWdlX2hhc19QYXJTIiwgICMgbnVtYmVyIG9mIHBvc2l0aXZlIGhpdHMKICB0b3RhbF9jb2wgPSAidG90YWwucGhhZ2UiLCAgICAjIHRvdGFsIG51bWJlciBwZXIgZ3JvdXAKICBwLmFkanVzdC5tZXRob2QgPSAiQkgiCikKCnQKCnQkc2lnIDwtIEZBTFNFCgp0JHNpZyA8LWlmZWxzZSh0JHAuYWRqPDAuMDUsIFRSVUUsIEZBTFNFKQpgYGAKCgpgYGB7cn0KbGlicmFyeShkcGx5cikKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGVtbWVhbnMpCmxpYnJhcnkoZ2dzaWduaWYpCgojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMgMe+4j+KDoyBQcmVwYXJlIHBsb3R0aW5nIGRhdGFmcmFtZQpwcm9iX2RmIDwtIGFzLmRhdGEuZnJhbWUocmVzJHByb2JhYmlsaXRpZXMpCmZpZy5tZXRhIDwtIG1lcmdlKHByb2JfZGYsIG1ldGEuY2F0cywgYnkgPSAiaG9zdF9waGFnZV9zcG9yIiwgYWxsID0gVFJVRSkKCiMgU2V0IGN1c3RvbSB4LWF4aXMgb3JkZXIKZmlnLm1ldGEkaG9zdF9waGFnZV9zcG9yIDwtIGZhY3RvcigKICBmaWcubWV0YSRob3N0X3BoYWdlX3Nwb3IsCiAgbGV2ZWxzID0gYygKICAgICdCYWNpbGxvdGFfTHl0aWNfU3BvcicsICdCYWNpbGxvdGFfVGVtcF9TcG9yJywgCiAgICAnQmFjaWxsb3RhX0x5dGljX05vblNwb3InLCAnQmFjaWxsb3RhX1RlbXBfTm9uU3BvcicsIAogICAgJ090aGVyUGh5bGFfTHl0aWNfTm9uU3BvcicsICdPdGhlclBoeWxhX1RlbXBfTm9uU3BvcicKICApLAogIG9yZGVyZWQgPSBUUlVFCikKCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyAy77iP4oOjIEV4dHJhY3QgcGFpcndpc2UgVHVrZXkgdGVzdHMKYGBgCnBhaXJzX2RmIDwtIGFzLmRhdGEuZnJhbWUocmVzJHBhaXJ3aXNlX3Rlc3RzKSAlPiUKICBtdXRhdGUoCiAgICBjb250cmFzdF9jaGFyID0gYXMuY2hhcmFjdGVyKGNvbnRyYXN0KSwKICAgIHBfbGFiZWwgPSBjYXNlX3doZW4oCiAgICAgIHAudmFsdWUgPCAwLjAwMSB+ICIqKioiLAogICAgICBwLnZhbHVlIDwgMC4wMSAgfiAiKioiLAogICAgICBwLnZhbHVlIDwgMC4wNSAgfiAiKiIsCiAgICAgIFRSVUUgICAgICAgICAgICB+ICJucyIKICAgICksCiAgICBncm91cDEgPSBzYXBwbHkoc3Ryc3BsaXQoY29udHJhc3RfY2hhciwgIiAvICIpLCBgW2AsIDEpLAogICAgZ3JvdXAyID0gc2FwcGx5KHN0cnNwbGl0KGNvbnRyYXN0X2NoYXIsICIgLyAiKSwgYFtgLCAyKQogICkKYGBge3J9CiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyAz77iP4oOjIENvbXB1dGUgeCBwb3NpdGlvbnMgYW5kIHNwYW4KcGFpcnNfZGYgPC0gYXMuZGF0YS5mcmFtZShyZXMkcGFpcndpc2VfdGVzdHMpICU+JQogIG11dGF0ZSgKICAgIGNvbnRyYXN0X2NoYXIgPSBhcy5jaGFyYWN0ZXIoY29udHJhc3QpLAogICAgcF9sYWJlbCA9IGNhc2Vfd2hlbigKICAgICAgcC52YWx1ZSA8IDAuMDAxIH4gIioqKiIsCiAgICAgIHAudmFsdWUgPCAwLjAxICB+ICIqKiIsCiAgICAgIHAudmFsdWUgPCAwLjA1ICB+ICIqIiwKICAgICAgVFJVRSAgICAgICAgICAgIH4gIm5zIgogICAgKSwKICAgIGdyb3VwMSA9IHNhcHBseShzdHJzcGxpdChjb250cmFzdF9jaGFyLCAiIC8gIiksIGBbYCwgMSksCiAgICBncm91cDIgPSBzYXBwbHkoc3Ryc3BsaXQoY29udHJhc3RfY2hhciwgIiAvICIpLCBgW2AsIDIpCiAgKQoKCgpwYWlyc19kZiA8LSBwYWlyc19kZiAlPiUKICBtdXRhdGUoCiAgICB4X251bTEgPSBhcy5udW1lcmljKGZhY3Rvcihncm91cDEsIGxldmVscyA9IGxldmVscyhmaWcubWV0YSRob3N0X3BoYWdlX3Nwb3IpKSksCiAgICB4X251bTIgPSBhcy5udW1lcmljKGZhY3Rvcihncm91cDIsIGxldmVscyA9IGxldmVscyhmaWcubWV0YSRob3N0X3BoYWdlX3Nwb3IpKSksCiAgICBzcGFuID0gYWJzKHhfbnVtMSAtIHhfbnVtMiksCiAgICB4X3BvcyA9ICh4X251bTEgKyB4X251bTIpIC8gMgogICkgJT4lCiAgYXJyYW5nZShzcGFuKQoKIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIDTvuI/ig6MgQ29tcHV0ZSB5IHBvc2l0aW9ucyBmb3IgbmVzdGVkIGJyYWNrZXRzCm9mZnNldF9zdGVwIDwtIDAuMDUKCnBhaXJzX2RmIDwtIHBhaXJzX2RmICU+JQogIHJvd3dpc2UoKSAlPiUKICBtdXRhdGUoCiAgICB5X2Jhc2UgPSBtYXgoCiAgICAgIGZpZy5tZXRhJGFzeW1wLlVDTFtmaWcubWV0YSRob3N0X3BoYWdlX3Nwb3IgJWluJSBjKGdyb3VwMSwgZ3JvdXAyKV0sCiAgICAgIG5hLnJtID0gVFJVRQogICAgKQogICkgJT4lCiAgdW5ncm91cCgpICU+JQogIGFycmFuZ2Uoc3BhbiwgZGVzYyhwX2xhYmVsKSkgJT4lICAjIHNob3J0ZXIgc3BhbnMgbG93ZXIsIG5zIGxvd2VyCiAgbXV0YXRlKAogICAgeV9wb3MgPSB5X2Jhc2UgKyAocm93X251bWJlcigpIC0gMSkgKiBvZmZzZXRfc3RlcAogICkKCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyA177iP4oOjIFByZXBhcmUgY29tcGFyaXNvbnMgbGlzdCBmb3IgZ2dzaWduaWYKY29tcGFyaXNvbnNfbGlzdCA8LSBsYXBwbHkoMTpucm93KHBhaXJzX2RmKSwgZnVuY3Rpb24oaSkgewogIGMoYXMuY2hhcmFjdGVyKHBhaXJzX2RmJGdyb3VwMVtpXSksIGFzLmNoYXJhY3RlcihwYWlyc19kZiRncm91cDJbaV0pKQp9KQoKIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIDbvuI/ig6MgUGxvdCB3aXRoIGRvZGdlIGFuZCBibGFjayBicmFja2V0cwpwZCA8LSBwb3NpdGlvbl9kb2RnZSh3aWR0aCA9IDAuNSkKCnRwbG90IDwtIGdncGxvdChmaWcubWV0YSwgYWVzKHggPSBob3N0X3BoYWdlX3Nwb3IsIHkgPSBwcm9iLCBjb2xvciA9IGJhYy5ob3N0KSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDMsIHBvc2l0aW9uID0gcGQpICsKICBnZW9tX2Vycm9yYmFyKGFlcyh5bWluID0gYXN5bXAuTENMLCB5bWF4ID0gYXN5bXAuVUNMKSwgd2lkdGggPSAwLjIsIHBvc2l0aW9uID0gcGQpICsKICBnZ3NpZ25pZjo6Z2VvbV9zaWduaWYoCiAgICBjb21wYXJpc29ucyA9IGNvbXBhcmlzb25zX2xpc3QsCiAgICBhbm5vdGF0aW9ucyA9IHBhaXJzX2RmJHBfbGFiZWwsCiAgICB5X3Bvc2l0aW9uID0gcGFpcnNfZGYkeV9wb3MsCiAgICB0aXBfbGVuZ3RoID0gMC4wMiwKICAgIHRleHRzaXplID0gNSwKICAgIGNvbG9yID0gImJsYWNrIgogICkgKwogIHlsaW0oMCwgbWF4KHBhaXJzX2RmJHlfcG9zICsgMC4wMikpICsKICAgeWxhYigiUHJvYmFiaWxpdHkgb2YgMSBvciBtb3JlIFBhclMgXG4gYmluZGluZyBzaXRlcyBpbiBwaGFnZSBnZW5vbWUiKSArCiAgeGxhYigiUGhhZ2UgTGlmZXN0eWxlIikgKwogIGxhYnMoY29sb3IgPSAiQmFjdGVyaWFsIEhvc3QiKSArCiAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxNCkgKwogIHNjYWxlX3hfZGlzY3JldGUoCiAgICBsYWJlbHMgPSBjKAogICAgICAnQmFjaWxsb3RhX0x5dGljX1Nwb3InICAgICA9ICdMeXRpYycsCiAgICAgICdCYWNpbGxvdGFfVGVtcF9TcG9yJyAgICAgID0gJ1RlbXBlcmF0ZScsCiAgICAgICdCYWNpbGxvdGFfTHl0aWNfTm9uU3BvcicgID0gJ0x5dGljJywKICAgICAgJ0JhY2lsbG90YV9UZW1wX05vblNwb3InICAgPSAnVGVtcGVyYXRlJywKICAgICAgJ090aGVyUGh5bGFfTHl0aWNfTm9uU3BvcicgPSAnTHl0aWMnLAogICAgICAnT3RoZXJQaHlsYV9UZW1wX05vblNwb3InICA9ICdUZW1wZXJhdGUnCiAgICApCiAgKQoKdHBsb3QKCgoKcGFpcnNfZGYyIDwtIHN1YnNldChwYWlyc19kZiwgcGFpcnNfZGYkY29udHJhc3Q9PSJCYWNpbGxvdGFfTHl0aWNfU3BvciAvIEJhY2lsbG90YV9UZW1wX1Nwb3IiIHwKICAgICAgICAgICAgICAgICAgICAgIHBhaXJzX2RmJGNvbnRyYXN0PT0gIkJhY2lsbG90YV9MeXRpY19TcG9yIC8gQmFjaWxsb3RhX0x5dGljX05vblNwb3IiIHwKICAgICAgICAgICAgICAgICAgICAgIHBhaXJzX2RmJGNvbnRyYXN0PT0iQmFjaWxsb3RhX0x5dGljX05vblNwb3IgLyBCYWNpbGxvdGFfVGVtcF9Ob25TcG9yIiB8CiAgICAgICAgICAgICAgICAgICAgICBwYWlyc19kZiRjb250cmFzdD09IkJhY2lsbG90YV9UZW1wX05vblNwb3IgLyBCYWNpbGxvdGFfVGVtcF9TcG9yIiB8CiAgICAgICAgICAgICAgICAgICAgICBwYWlyc19kZiRjb250cmFzdD09Ik90aGVyUGh5bGFfTHl0aWNfTm9uU3BvciAvIE90aGVyUGh5bGFfVGVtcF9Ob25TcG9yIiB8CiAgICAgICAgICAgICAgICAgICAgICBwYWlyc19kZiRjb250cmFzdD09IkJhY2lsbG90YV9MeXRpY19TcG9yIC8gT3RoZXJQaHlsYV9MeXRpY19Ob25TcG9yIiB8CiAgICAgICAgICAgICAgICAgICAgICBwYWlyc19kZiRjb250cmFzdD09Ik90aGVyUGh5bGFfTHl0aWNfTm9uU3BvciAvIE90aGVyUGh5bGFfVGVtcF9Ob25TcG9yInwKICAgICAgICAgICAgICAgICAgICAgIHBhaXJzX2RmJGNvbnRyYXN0PT0iQmFjaWxsb3RhX1RlbXBfU3BvciAvIE90aGVyUGh5bGFfVGVtcF9Ob25TcG9yIikKI0JhY2lsbG90YV9MeXRpY19TcG9yIC8gT3RoZXJQaHlsYV9MeXRpY19Ob25TcG9yCgpwYWlyc19kZjIgPC0gcGFpcnNfZGYyICU+JQogIG11dGF0ZSgKICAgIHhfbnVtMSA9IGFzLm51bWVyaWMoZmFjdG9yKGdyb3VwMSwgbGV2ZWxzID0gbGV2ZWxzKGZpZy5tZXRhJGhvc3RfcGhhZ2Vfc3BvcikpKSwKICAgIHhfbnVtMiA9IGFzLm51bWVyaWMoZmFjdG9yKGdyb3VwMiwgbGV2ZWxzID0gbGV2ZWxzKGZpZy5tZXRhJGhvc3RfcGhhZ2Vfc3BvcikpKSwKICAgIHNwYW4gPSBhYnMoeF9udW0xIC0geF9udW0yKSwgICMgZGlzdGFuY2UgYmV0d2VlbiBncm91cHMKICAgIHhfcG9zID0gKHhfbnVtMSArIHhfbnVtMikgLyAyICAjIGNlbnRlciBmb3IgYnJhY2tldAogICkgJT4lCiAgYXJyYW5nZShzcGFuKSAgIyBzbWFsbGVyIHNwYW5zIGZpcnN0CgojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMgNO+4j+KDoyBDb21wdXRlIHkgcG9zaXRpb25zIGZvciBuZXN0ZWQgYnJhY2tldHMKCnBhaXJzX2RmMiA8LSBwYWlyc19kZjIgJT4lCiAgcm93d2lzZSgpICU+JQogIG11dGF0ZSgKICAgIHlfYmFzZSA9IG1heCgKICAgICAgZmlnLm1ldGEkYXN5bXAuVUNMW2ZpZy5tZXRhJGhvc3RfcGhhZ2Vfc3BvciAlaW4lIGMoZ3JvdXAxLCBncm91cDIpXSwKICAgICAgbmEucm0gPSBUUlVFCiAgICApCiAgKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgbXV0YXRlKAogICAgeV9wb3MgPSB5X2Jhc2UgKyAocm93X251bWJlcigpIC0gMSkgKiBvZmZzZXRfc3RlcCAgIyBzdGFjayBieSByb3cgb3JkZXIgKHNtYWxsZXIgc3BhbiBsb3dlcikKICApCgojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMgNe+4j+KDoyBQcmVwYXJlIGNvbXBhcmlzb25zIGxpc3QgZm9yIGdnc2lnbmlmCmNvbXBhcmlzb25zX2xpc3QgPC0gbGFwcGx5KDE6bnJvdyhwYWlyc19kZjIpLCBmdW5jdGlvbihpKSB7CiAgYyhhcy5jaGFyYWN0ZXIocGFpcnNfZGYyJGdyb3VwMVtpXSksIGFzLmNoYXJhY3RlcihwYWlyc19kZjIkZ3JvdXAyW2ldKSkKfSkKCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyA277iP4oOjIFBsb3Qgd2l0aCBkb2RnZSBmb3IgbXVsdGlwbGUgaG9zdHMKcGQgPC0gcG9zaXRpb25fZG9kZ2Uod2lkdGggPSAwLjUpICAjIGFkanVzdCB3aWR0aCBmb3Igc2VwYXJhdGlvbiBvZiBwb2ludHMKCnRwbG90MiA8LSBnZ3Bsb3QoZmlnLm1ldGEsIGFlcyh4ID0gaG9zdF9waGFnZV9zcG9yLCB5ID0gcHJvYiwgY29sb3IgPSBiYWMuaG9zdCkpICsKICBnZW9tX3BvaW50KHNpemUgPSAzLCBwb3NpdGlvbiA9IHBkKSArCiAgZ2VvbV9lcnJvcmJhcihhZXMoeW1pbiA9IGFzeW1wLkxDTCwgeW1heCA9IGFzeW1wLlVDTCksIHdpZHRoID0gMC4yLCBwb3NpdGlvbiA9IHBkKSArCiAgZ2dzaWduaWY6Omdlb21fc2lnbmlmKAogICAgY29tcGFyaXNvbnMgPSBjb21wYXJpc29uc19saXN0LAogICAgYW5ub3RhdGlvbnMgPSBwYWlyc19kZjIkcF9sYWJlbCwKICAgIHlfcG9zaXRpb24gPSBwYWlyc19kZjIkeV9wb3MsCiAgICB0aXBfbGVuZ3RoID0gMC4wMiwKICAgIHRleHRzaXplID0gNSwKICAgIGNvbG9yID0gImJsYWNrIgogICkgKwogIHlsaW0oMCwgbWF4KHBhaXJzX2RmMiR5X3BvcyArIDAuMDIpKSArICAjIGV4dGVuZCB5LWF4aXMgdG8gZml0IHRvcCBicmFja2V0cwogICB5bGFiKCJQcm9iYWJpbGl0eSBvZiAxIG9yIG1vcmUgUGFyUyBcbiBiaW5kaW5nIHNpdGVzIGluIHBoYWdlIGdlbm9tZSIpICsKICB4bGFiKCIiKSArCiAgbGFicyhjb2xvciA9ICJCYWN0ZXJpYWwgSG9zdCIpICsKICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZSA9IDE0KStzY2FsZV94X2Rpc2NyZXRlKAogICAgbGFiZWxzID0gYygKICAgICAgJ0JhY2lsbG90YV9MeXRpY19TcG9yJyAgICA9ICdMeXRpYycsCiAgICAgICdCYWNpbGxvdGFfVGVtcF9TcG9yJyAgICAgPSAnVGVtcGVyYXRlJywKICAgICAgJ0JhY2lsbG90YV9MeXRpY19Ob25TcG9yJyA9ICdMeXRpYycsCiAgICAgICdCYWNpbGxvdGFfVGVtcF9Ob25TcG9yJyAgPSAnVGVtcGVyYXRlJywKICAgICAgJ090aGVyUGh5bGFfTHl0aWNfTm9uU3Bvcic9ICdMeXRpYycsCiAgICAgICdPdGhlclBoeWxhX1RlbXBfTm9uU3BvcicgPSAnVGVtcGVyYXRlJwogICAgKQogICkgKyB0aGVtZShwbG90Lm1hcmdpbiA9IG1hcmdpbigxMCwgMTAsIDMwLCAxMCkgICMgbGFyZ2UgYm90dG9tIG1hcmdpbiBmb3IgbGFiZWxzCiAgKSArCiAgY29vcmRfY2FydGVzaWFuKGNsaXAgPSAib2ZmIikgK3RoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpCgp0cGxvdDIKCgp0cGxvdDIKCgoKCiMgVGhyZWUgbGFiZWxzIGV2ZW5seSBzcGFjZWQgYWNyb3NzIHRoZSBheGlzCnNwb3JfbGFiZWwgPC0gdGV4dEdyb2IoCiAgIlNwb3J1bGF0aW5nIEJhY2lsbGlvdGEgSG9zdCIsIGdwID0gZ3Bhcihmb250c2l6ZSA9IDEyLCBmb250ZmFjZSA9ICJib2xkIiksCiAgeCA9IDEvNiwgeSA9IC0uMDUsIGp1c3QgPSAiY2VudGVyIgopCgpub25zcG9yX2xhYmVsIDwtIHRleHRHcm9iKAogICJOb24tU3BvcnVsYXRpbmcgQmFjaWxsaW90YSBIb3N0IiwgZ3AgPSBncGFyKGZvbnRzaXplID0gMTIsIGZvbnRmYWNlID0gImJvbGQiKSwKICB4ID0gMy82LCB5ID0gLS4wNSwganVzdCA9ICJjZW50ZXIiCikKCm90aGVyX2xhYmVsIDwtIHRleHRHcm9iKAogICJOb24tQmFjaWxsaW90YSBIb3N0IiwgZ3AgPSBncGFyKGZvbnRzaXplID0gMTIsIGZvbnRmYWNlID0gImJvbGQiKSwKICB4ID0gNS82LCB5ID0gLS4wNSwganVzdCA9ICJjZW50ZXIiCikKCnRwbG90X2ZpbmFsMiA8LSB0cGxvdDIgKwogIGFubm90YXRpb25fY3VzdG9tKHNwb3JfbGFiZWwpICsKICBhbm5vdGF0aW9uX2N1c3RvbShub25zcG9yX2xhYmVsKSArCiBhbm5vdGF0aW9uX2N1c3RvbShvdGhlcl9sYWJlbCkgCgp0cGxvdF9maW5hbDIKCgoKCgoKYGBgCgpgYGB7cn0KdHBsb3QKCnRwbG90X2ZpbmFsMgoKZ2dzYXZlKGhlcmUoIjAyX21vdGlmcy9QYXJTL2ZpZ3Mvc2lnbmZpY2FuY2VfcHJvYl9hbGwucG5nIiksIHdpZHRoID0gMTAsIGhlaWdodCA9IDEwKQoKYGBgCg==